package com.workcell.workdata.xthings.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.workcell.workdata.xthings.constant.CodeChainnumUseStateEnum;
import com.workcell.workdata.xthings.constant.HardwareTypeEnum;
import com.workcell.workdata.xthings.constant.KafKaTopicConstant;
import com.workcell.workdata.xthings.dto.BindInfoDto;
import com.workcell.workdata.xthings.dto.DeviceBindDto;
import com.workcell.workdata.xthings.dto.DeviceReplaceVo;
import com.workcell.workdata.xthings.dto.MqSendMessageDTO;
import com.workcell.workdata.xthings.entity.BindType;
import com.workcell.workdata.xthings.entity.CodeChainnum;
import com.workcell.workdata.xthings.entity.CodePowermeter;
import com.workcell.workdata.xthings.entity.CodeValve;
import com.workcell.workdata.xthings.entity.DeviceBind;
import com.workcell.workdata.xthings.kafka.sender.KafkaSendService;
import com.workcell.workdata.xthings.mapper.CodePowermeterMapper;
import com.workcell.workdata.xthings.mapper.CodeValveMapper;
import com.workcell.workdata.xthings.mapper.DeviceBindMapper;
import com.workcell.workdata.xthings.service.BindTypeService;
import com.workcell.workdata.xthings.service.CodeChainnumService;
import com.workcell.workdata.xthings.service.DeviceBindService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author Admin
 * @description 针对表【t_device_bind(设备绑定表)】的数据库操作Service实现
 * @createDate 2023-03-17 18:28:56
 */
@Slf4j
@Service
public class DeviceBindServiceImpl extends ServiceImpl<DeviceBindMapper, DeviceBind> implements DeviceBindService {
    @Resource
    private DeviceBindMapper deviceBindMapper;
    @Resource
    private CodeChainnumService codeChainnumService;
    @Resource
    private BindTypeService bindTypeService;
    @Resource
    private CodePowermeterMapper codePowermeterMapper;
	@Resource
    private CodeValveMapper codeValveMapper;
	@Resource
	private KafkaSendService kafkaSendService;


    //串口设备绑定数量限制
    private static final Integer DEVICE_LIMIT = 30;
    //子设备机器码限制最小值
    public static final Integer DEVICE_ADDRESS_MIN = 1;
    //子设备机器码限制最大值
    public static final Integer DEVICE_ADDRESS_MAX = 247;


    @Override
    public DeviceBind getOne(DeviceBind deviceBind) {
        LambdaQueryWrapper<DeviceBind> lambdaQueryWrapper = Wrappers.lambdaQuery(DeviceBind.class)
                .setEntity(deviceBind);
        return deviceBindMapper.selectOne(lambdaQueryWrapper);
    }

    @Override
    public List<DeviceBind> list(DeviceBind deviceBind) {
        LambdaQueryWrapper<DeviceBind> lambdaQueryWrapper = Wrappers.lambdaQuery(DeviceBind.class)
                .setEntity(deviceBind);
        return deviceBindMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 设备绑定
     *
     * @param deviceBind
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean bind(DeviceBind deviceBind) {
        // 入参
        String masterDeviceNo = deviceBind.getMasterDeviceNo();
        String masterHardwareType = deviceBind.getMasterHardwareType();
        String slaveDeviceNo = deviceBind.getSlaveDeviceNo();
        String slaverHardwareType = deviceBind.getSlaverHardwareType();

        // 判断设备类型入参
        HardwareTypeEnum masterHardwareTypeEnum = HardwareTypeEnum.getByCode(masterHardwareType);
        Assert.isTrue(masterHardwareTypeEnum != HardwareTypeEnum.OVER, "主设备类型编码{}不存在", masterHardwareType);
        HardwareTypeEnum slaveHardwareTypeEnum = HardwareTypeEnum.getByCode(slaverHardwareType);
        Assert.isTrue(slaveHardwareTypeEnum != HardwareTypeEnum.OVER, "从设备类型编码{}不存在", slaverHardwareType);

        // 判断主设备类型是否支持绑定从设备类型
        boolean checkBind = bindTypeService.checkBind(masterHardwareType, slaverHardwareType);
        Assert.isTrue(checkBind, "当前两个设备不支持绑定");

        // 设备是否存在判断
        CodeChainnum masterCodeChainnum = codeChainnumService.getByDeviceNoAndHardwareType(masterDeviceNo, masterHardwareType);
        Assert.notNull(masterCodeChainnum, "不存在设备编号为{}的{}", masterDeviceNo, masterHardwareTypeEnum.getMsg());
        CodeChainnum slaverCodeChainnum = codeChainnumService.getByDeviceNoAndHardwareType(slaveDeviceNo, slaverHardwareType);
        Assert.notNull(slaverCodeChainnum, "不存在设备编号为{}的{}", slaveDeviceNo, slaveHardwareTypeEnum.getMsg());

        // 从设备是否已绑定判断
        List<DeviceBindDto> deviceBindDtoList = deviceBindMapper.bindList();
        DeviceBindDto deviceBindDto = deviceBindDtoList.stream()
                .filter(v -> StrUtil.equals(v.getSlaveDeviceNo(), slaveDeviceNo))
                .findAny()
                .orElse(null);
        if (deviceBindDto != null) {
            throw new RuntimeException(StrUtil.format("当前设备已与编号为{}的主设备绑定", deviceBindDto.getMasterDeviceNo()));
        }

        List<CodeChainnum> codeChainnumList = deviceBindMapper.deviceBindList(masterDeviceNo);

        // 串口设备是否已达绑定设备上限判断
        Assert.isTrue(codeChainnumList.size() < DEVICE_LIMIT, "当前主设备已达绑定个数上限{}", DEVICE_LIMIT);

        // 是否录入通讯地址的判断
        String addressId = slaverCodeChainnum.getAddressId();
        Assert.notNull(addressId, "当前从设备未录入机器码");

        // 从设备机器码范围判断
//        Assert.checkBetween(Integer.parseInt(addressId), DEVICE_ADDRESS_MIN, DEVICE_ADDRESS_MAX, "从设备机器码不在范围{}-{}", DEVICE_ADDRESS_MIN, DEVICE_ADDRESS_MAX);

        // 主设备绑定的机器码是否重复判断
        boolean noneMatch = codeChainnumList.stream()
                .noneMatch(v -> StrUtil.equals(v.getAddressId(), addressId));
        Assert.isTrue(noneMatch, "当前主设备已绑定机器码{}", addressId);

        //进行设备绑定
        boolean bindBoolean = this.save(DeviceBind.builder()
                .masterDeviceNo(masterDeviceNo)
                .masterHardwareType(masterHardwareType)
                .slaveDeviceNo(slaveDeviceNo)
                .slaverHardwareType(slaverHardwareType)
                .build());
        Assert.isTrue(bindBoolean, "设备绑定失败");

        //绑定后将 主从设备 设置为已激活
        if (codeChainnumList.size() == 0) {
            LambdaUpdateWrapper<CodeChainnum> lambdaUpdateWrapper = Wrappers.lambdaUpdate(CodeChainnum.class)
                    .eq(CodeChainnum::getId, masterCodeChainnum.getId())
                    .set(CodeChainnum::getUseState, CodeChainnumUseStateEnum.ACTIVATED.getCode())
                    .set(CodeChainnum::getActiveTime, LocalDateTime.now());
            boolean master = codeChainnumService.update(lambdaUpdateWrapper);
            Assert.isTrue(master, "主设备激活失败");
        }
        LambdaUpdateWrapper<CodeChainnum> lambdaUpdateWrapper = Wrappers.lambdaUpdate(CodeChainnum.class)
                .eq(CodeChainnum::getId, slaverCodeChainnum.getId())
                .set(CodeChainnum::getUseState, CodeChainnumUseStateEnum.ACTIVATED.getCode())
                .set(CodeChainnum::getActiveTime, LocalDateTime.now());
        boolean slaver = codeChainnumService.update(lambdaUpdateWrapper);
        Assert.isTrue(slaver, "从设备激活失败");
        return true;
    }

    /**
     * 设备解绑
     *
     * @param deviceBind
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean unbind(DeviceBind deviceBind) {
        // 判断设备类型入参
        HardwareTypeEnum masterHardwareTypeEnum = HardwareTypeEnum.getByCode(deviceBind.getMasterHardwareType());
        Assert.isTrue(masterHardwareTypeEnum != HardwareTypeEnum.OVER, "主设备类型编码{}不存在", deviceBind.getMasterDeviceNo());
        HardwareTypeEnum slaveHardwareTypeEnum = HardwareTypeEnum.getByCode(deviceBind.getSlaverHardwareType());
        Assert.isTrue(slaveHardwareTypeEnum != HardwareTypeEnum.OVER, "从设备类型编码{}不存在", deviceBind.getSlaveDeviceNo());

        // 设备是否存在判断
        CodeChainnum masterCodeChainnum = codeChainnumService.getByDeviceNoAndHardwareType(deviceBind.getMasterDeviceNo(), deviceBind.getMasterHardwareType());
        Assert.notNull(masterCodeChainnum, "不存在设备编号为{}的{}", deviceBind.getMasterDeviceNo(), masterHardwareTypeEnum.getMsg());
        CodeChainnum slaverCodeChainnum = codeChainnumService.getByDeviceNoAndHardwareType(deviceBind.getSlaveDeviceNo(), deviceBind.getSlaverHardwareType());
        Assert.notNull(slaverCodeChainnum, "不存在设备编号为{}的{}", deviceBind.getSlaveDeviceNo(), slaveHardwareTypeEnum.getMsg());

        // 查询设备绑定关系
        DeviceBind deviceBindOld = this.getOne(deviceBind);
        Assert.notNull(deviceBindOld, "当前两个设备不存在绑定关系");

        // 进行解绑
        LambdaUpdateWrapper<DeviceBind> lambdaUpdateWrapper = Wrappers.lambdaUpdate(DeviceBind.class)
                .setEntity(deviceBind);
        return this.remove(lambdaUpdateWrapper);
    }

    /**
     * 查询设备绑定列表
     *
     * @return 设备绑定列表
     */
    @Override
    public List<CodeChainnum> bindList2(String deviceNo, String hardwareType) {
        List<BindType> bindTypeList = bindTypeService.slaverTypesByMasterType(hardwareType);
        boolean anyMatch = bindTypeList.stream()
                .anyMatch(v -> StrUtil.equals(v.getMasterType(), hardwareType));
        Assert.isTrue(anyMatch, "当前设备不支持绑定其他设备");
        return deviceBindMapper.deviceBindList(deviceNo);
    }

    /**
     * 获取设备信息
     *
     * @param masterDeviceNo 主设备编码
     * @param addressId      从设备机器码
     * @return 设备信息
     */
    @Override
    public BindInfoDto getBindInfo(String masterDeviceNo, String addressId) {
        BindInfoDto bindInfo = deviceBindMapper.getBindInfo(masterDeviceNo, addressId);
        if (ObjectUtil.isNotEmpty(bindInfo)) {
            if (HardwareTypeEnum.POWER_METER.getCode().equals(bindInfo.getSlaverHardwareType())) {
                LambdaQueryWrapper<CodePowermeter> lambdaQueryWrapper = Wrappers.lambdaQuery(CodePowermeter.class)
                        .eq(CodePowermeter::getChainNum, bindInfo.getChainNum());
                CodePowermeter codePowermeter = codePowermeterMapper.selectOne(lambdaQueryWrapper);
                if (ObjectUtil.isNotEmpty(codePowermeter)) {
                    bindInfo.setCoefficient(codePowermeter.getCoefficient());
                }
            }else if (HardwareTypeEnum.VALVE.getCode().equals(bindInfo.getSlaverHardwareType())) {
				LambdaQueryWrapper<CodeValve> lambdaQueryWrapper = Wrappers.lambdaQuery(CodeValve.class)
						.eq(CodeValve::getChainNum, bindInfo.getChainNum());
				CodeValve codeValve = codeValveMapper.selectOne(lambdaQueryWrapper);
				if (ObjectUtil.isNotEmpty(codeValve)) {
					bindInfo.setDeviceStatus(codeValve.getState());
				}
			}
        }
        return bindInfo;
    }

    /**
     * 根据短链码查询设备绑定信息
     *
     * @param chainNum 短链码
     * @return
     */
    @Override
    public List<CodeChainnum> bindList2(String chainNum) {
        return deviceBindMapper.bindList2(chainNum);
    }

    @Override
    public List<DeviceBindDto> allBindList() {
        return deviceBindMapper.allBindList();
    }

	@Override
	public void sendModbusJobToKafka(){
		log.info("Xthings串口调度日志开始。");
		// 查询所有的串口设备
		List<DeviceBindDto> deviceBindDtoList = deviceBindMapper.allBindList();
		// 按串口设备分组
		Map<String, List<DeviceBindDto>> deviceBindDtoMap = deviceBindDtoList.stream()
				.collect(Collectors.groupingBy(DeviceBindDto::getMasterDeviceNo));
		// 推送kafka消息
		deviceBindDtoMap.forEach((k,value)->{
			MqSendMessageDTO mqSendMessageDTO = new MqSendMessageDTO();
			mqSendMessageDTO.setTopic(KafKaTopicConstant.XTHINGS_SEND_MODBUS_JOB_TO_KAFKA);
			mqSendMessageDTO.setContent(JSON.toJSONString(value));
			kafkaSendService.sendMqMessage(mqSendMessageDTO);
		});
		log.info("Xthings串口调度日志结束。");
	}

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean replace(DeviceReplaceVo deviceReplaceVo) {
        String masterDeviceNo = deviceReplaceVo.getMasterDeviceNo();
        String masterHardwareType = deviceReplaceVo.getMasterHardwareType();
        String oldSlaveDeviceNo = deviceReplaceVo.getOldSlaveDeviceNo();
        String oldSlaveHardwareType = deviceReplaceVo.getOldSlaveHardwareType();
        String newSlaveDeviceNo = deviceReplaceVo.getNewSlaveDeviceNo();
        String newSlaveHardwareType = deviceReplaceVo.getNewSlaveHardwareType();

        // 判断新旧子设备编码是否不同
        Assert.notEquals(oldSlaveDeviceNo, newSlaveDeviceNo, "新旧子设备编码不能相同");

        // 查询主站设备是否存在
        CodeChainnum codeChainnum = codeChainnumService.getByDeviceNoAndHardwareType(masterDeviceNo, masterHardwareType);
        Assert.notNull(codeChainnum, "不存在编号为{}的主设备", masterDeviceNo);

        // 查询旧子设备是否存在
        CodeChainnum oldCodeChainnum = codeChainnumService.getByDeviceNoAndHardwareType(oldSlaveDeviceNo, oldSlaveHardwareType);
        Assert.notNull(oldCodeChainnum, "不存在编号为{}的旧子设备", oldSlaveDeviceNo);

        // 查询新子设备是否存在
        CodeChainnum newCodeChainnum = codeChainnumService.getByDeviceNoAndHardwareType(newSlaveDeviceNo, newSlaveHardwareType);
        Assert.notNull(newCodeChainnum, "不存在编号为{}的新子设备", newSlaveDeviceNo);

        // 查询新子设备是否是支持绑定的类型
        boolean checkBind = bindTypeService.checkBind(masterHardwareType, newSlaveHardwareType);
        Assert.isTrue(checkBind, "新子设备不支持绑定，请添加绑定类型");

        // 查询旧子设备是否已被绑定
        DeviceBind oldDeviceBind = this.getOne(
                DeviceBind.builder()
                        .masterDeviceNo(masterDeviceNo)
                        .slaveDeviceNo(oldSlaveDeviceNo)
                        .build()
        );
        Assert.notNull(oldDeviceBind, "旧子设备未被当前主设备绑定");

        // 查询新子设备是否未被绑定
        DeviceBind newDeviceBind = this.getOne(
                DeviceBind.builder()
                        .masterDeviceNo(masterDeviceNo)
                        .slaveDeviceNo(newSlaveDeviceNo)
                        .build()
        );
        Assert.isNull(newDeviceBind, "新子设备已被绑定");

        // 进行设备替换
        LambdaUpdateWrapper<DeviceBind> lambdaUpdateWrapper = Wrappers.lambdaUpdate(DeviceBind.class)
                .eq(DeviceBind::getMasterDeviceNo, masterDeviceNo)
                .eq(DeviceBind::getMasterHardwareType, masterHardwareType)
                .eq(DeviceBind::getSlaveDeviceNo, oldSlaveDeviceNo)
                .eq(DeviceBind::getSlaverHardwareType, oldSlaveHardwareType)
                .set(DeviceBind::getSlaveDeviceNo, newSlaveDeviceNo)
                .set(DeviceBind::getSlaverHardwareType, newSlaveHardwareType);
        return deviceBindMapper.update(null, lambdaUpdateWrapper) > 0;
    }

    @Override
    public boolean removeByDeviceNo(String deviceNo) {
        LambdaUpdateWrapper<DeviceBind> lambdaUpdateWrapper = Wrappers.lambdaUpdate(DeviceBind.class)
                .eq(DeviceBind::getMasterDeviceNo, deviceNo)
                .or()
                .eq(DeviceBind::getSlaveDeviceNo, deviceNo);
        return deviceBindMapper.delete(lambdaUpdateWrapper) > 0;
    }

	@Override
	public DeviceBindDto getBindInfoByDeviceNo(String deviceNo,String queryType) {
		return deviceBindMapper.getBindInfoByDeviceNo(deviceNo,queryType);
	}
}




